/*
 * libtu/numparser2.h
 *
 * Copyright (c) Tuomo Valkonen 1999-2002. 
 *
 * You may distribute and modify this library under the terms of either
 * the Clarified Artistic License or the GNU LGPL, version 2.1 or later.
 */

#define MAX_MANTISSA 10  /* should be enough for our needs */
#define ULONG_SIZE (sizeof(ulong)*8)

enum{
	NPNUM_INT,
	NPNUM_FLOAT
};

#ifdef NP_SIMPLE_IMPL

typedef struct _NPNum{
	int type;
	int base;
 	bool negative;
	double fval;
	ulong ival;
} NPNum;

#define NUM_INIT {0, 0, 0, 0.0, 0}

static int npnum_mulbase_add(NPNum *num, long base, long v)
{
	double iold=num->ival;
	
	num->fval=num->fval*base+(double)v;
	
	num->ival*=base;
	
	if(num->ival<iold)
		num->type=NPNUM_FLOAT;
	
	num->ival+=v;
	
	return 0;
}

#else /* NP_SIMPLE_IMPL */

typedef struct _NPNum{
	unsigned char nmantissa;
	int type;
	int base;
 	bool negative;
	ulong mantissa[MAX_MANTISSA];
	long exponent;
} NPNum;

#define NUM_INIT {0, 0, 0, 0, {0,}, 0}

#define ADD_EXP(NUM, X) (NUM)->exponent+=(X);

#if defined(__GNUG__) && defined(i386) && !defined(NP_NO_I386_ASM)
 #define NP_I386_ASM
#endif

static int npnum_mulbase_add(NPNum *num, long base, long v)
{
	long i, j;
	ulong overflow;
#ifndef NP_I386_ASM
	ulong val;
#endif
	
	for(i=num->nmantissa;i>=0;i--){
#ifdef NP_I386_ASM
		__asm__("mul %4\n"
				: "=a" (num->mantissa[i]), "=d" (overflow)
				: "0" (num->mantissa[i]), "1" (0), "q" (base)
				: "eax", "edx");
#else
		overflow=0;
		val=num->mantissa[i];
		
		if(val<ULONG_MAX/base){
			val*=base;
		}else if(val){
			ulong tmp=val;
			ulong old=val;
			for(j=0; j<base; j++){
				val+=tmp;
				if(val<=old)
					overflow++;
				old=val;
			}
		}
		num->mantissa[i]=val;
#endif
		if(overflow){
			if(i==num->nmantissa){
				if(num->nmantissa==MAX_MANTISSA)
					return E_TOKZ_TOOBIG;
				num->nmantissa++;
			}
			num->mantissa[i+1]+=overflow;
		}
	}
	num->mantissa[0]+=v;
	
	return 0;
}

#undef NP_I386_ASM

#endif /* NP_SIMPLE_IMPL */


/* */


static bool is_end(int c)
{
	/* oops... EOF was missing */
	return (c==EOF || (c!='.' && ispunct(c)) || isspace(c) || iscntrl(c));
}


/* */


static int parse_exponent(NPNum *num, Tokenizer *tokz, int c)
{
	long exp=0;
	bool neg=FALSE;
	int err=0;
	
	c=GETCH();
	
	if(c=='-' || c=='+'){
		if(c=='-')
			neg=TRUE;
		c=GETCH();
	}
	
	for(; 1; c=GETCH()){
		if(isdigit(c)){
			exp*=10;
			exp+=c-'0';
		}else if(is_end(c)){
			UNGETCH(c);
			break;
		}else{
			err=E_TOKZ_NUMFMT;
		}
	}

	if(neg)
		exp*=-1;

#ifndef NP_SIMPLE_IMPL
	ADD_EXP(num, exp);
#else
	num->fval*=pow(num->base, exp);
#endif	
	return err;
}


static int parse_number(NPNum *num, Tokenizer *tokz, int c)
{
	int base=10;
	int dm=1;
	int err=0;
	int tmp;
#ifdef NP_SIMPLE_IMPL
	double divisor=base;
#endif	
	
	if(c=='-' || c=='+'){
		if(c=='-')
			num->negative=TRUE;
		c=GETCH();
		if(!isdigit(c))
			err=E_TOKZ_NUMFMT;
	}
	
	if(c=='0'){
		dm=0;
		c=GETCH();
		if(c=='x' || c=='X'){
			base=16;
			c=GETCH();
		}else if(c=='b' || c=='B'){
			base=2;
			c=GETCH();
		}else if('0'<=c && c<='7'){
			base=8;
		}else{
			dm=2;
		}
	}
	
	num->base=base;
	
	for(; 1; c=GETCH()){
		if((c=='e' || c=='E') && dm!=0){
			if(dm<2){
				err=E_TOKZ_NUMFMT;
				continue;
			}
			tmp=parse_exponent(num, tokz, c);
			if(err==0)
				err=tmp;
			break;
		}
		
		if(isxdigit(c)){
			if('0'<=c && c<='9')
				c-='0';
			else if(isupper(c))
				c-='A'-10;
			else
				c-='a'-10;
			
			if(c>=base)
				err=E_TOKZ_NUMFMT;

#ifdef NP_SIMPLE_IMPL
			if(dm==3){
				num->fval+=(double)c/divisor;
				divisor*=base;
			}else
#endif
			{
				tmp=npnum_mulbase_add(num, base, c);
				if(err==0)
					err=tmp;
			}
			
			if(dm==1)
				dm=2;
#ifndef NP_SIMPLE_IMPL			
			else if(dm==3)
				ADD_EXP(num, -1);
#endif			
			continue;
		}
		
		if(c=='.'){
			if(dm!=2){
				err=E_TOKZ_NUMFMT;
			}
			dm=3;
#ifdef NP_SIMPLE_IMPL
			num->type=NPNUM_FLOAT;
			divisor=base;
#endif
			continue;
		}
		
		if(is_end(c)){
			UNGETCH(c);
			break;
		}
		
		err=E_TOKZ_NUMFMT;
	}
	
#ifndef NP_SIMPLE_IMPL			
	num->type=(num->exponent==0 ? NPNUM_INT : NPNUM_FLOAT);
#endif

	return err;
}
